<?php
namespace DRY\Render;

/**
 * DonReY Framework 2012 :: Render Transform - CSS
 * Generates valid CSS out of a set of selectors and properties in a table array
 *
 * @author Wavetrex <wavetrex@gmail.com>
 * @link http://www.donrey.net/Documentation/Structure/Modules/Render/css.html
 * @license [GPL v3] http://www.gnu.org/licenses/gpl-3.0.html
 * @version 1.0.dev
 * @package DonReY
 * @subpackage Module.Render
 */
class Transform_Css implements Transform_base
{

	/**
	 * Converts integers, floats and strings (which might contain %) into always 2-char hex value ( from 00 to FF )
	 * - clamps Out-of-range values
	 *
	 * @param int|string|float $r Red component
	 * @param int|string|float $g Green component
	 * @param int|string|float $b Blue component
	 */
	public function hex($r, $g, $b)
	{
		$components = array($r, $g, $b);
		$color = '#';
		foreach($components as $c) {
			if(is_string($c) && strpos($c, '%')!==false) // attempt to detect as percent %
				$c = (int)round(255*floatval($c)/100); // from 0% to 100%
			elseif(is_float($c))
				$c = (int)round(255*$c); // from 0 to 1 ( 1 = 255 )
			else
				$c = intval($c);
			// clamp $c from 0 to 255
			if($c<0) $c=0; if($c>255) $c=255;
			// and create the hex color string
			$color.= sprintf("%02X", $c);
		}
		return $color;
	}

	/**
	 * Helper that converts various types of values and strings into a proper integer or percent for the CSS rgb() function
	 *
	 * @param mixed $c
	 */
	private function rgb_Convert_Helper($c)
	{
		// convert all to floats first, to be able to clamp
		if(is_string($c) && strpos($c, '%')!==false) { // attempt to detect as percent %
			$c = floatval($c)/100;
			$percent = true; // will end up as percent, not int
		} elseif(is_float($c)) {
			// $c is unchanged when float
		} else
			// for everything else, attempt to consider as an int from 0 to 255
			$c = floatval($c)/255;
		// clamp $c from 0 to 1
		if($c<0) $c=0; if($c>1) $c=1;

		// convert to int or percent
		if(isset($percent))
			return ''.round($c * 100, 1).'%';
		else
			return ''.round($c * 255);
	}

	/**
	 * Creates a valid CSS function rgb() out of various types of PHP values
	 * - clamps Out-of-range values
	 *
	 * @param int|string|float $r Red component
	 * @param int|string|float $g Green component
	 * @param int|string|float $b Blue component
	 */
	public function rgb($r, $g, $b)
	{
		$components = array($r, $g, $b);
		foreach($components as &$c)
			$c = $this-> rgb_Convert_Helper($c);

		return "rgb(".implode(',', $components).")";
	}

	/**
	 * Creates a valid CSS function rgba() out of various types of PHP values
	 * - clamps Out-of-range values
	 *
	 * @param int|string|float $r Red component
	 * @param int|string|float $g Green component
	 * @param int|string|float $b Blue component
	 * @param float|string $a Alpha value
	 */
	public function rgba($r, $g, $b, $alpha)
	{
		$components = array($r, $g, $b);
		foreach($components as &$c)
			$c = $this-> rgb_Convert_Helper($c);

		// Alpha converting/clamping
		if(is_string($alpha) && strpos($alpha, '%')!==false) // attempt to detect as percent %
			$alpha = round(floatval($alpha)/100, 3);
		else
			// for everything else, attempt to consider as float
			$alpha = round(floatval($alpha),3);

		// clamp $alpha from 0 to 1
		if($alpha<0) $alpha=0; if($alpha>1) $alpha=1;

		return "rgba(".implode(',', $components).",$alpha)";
	}

	// TODO: Support hsl() and hsla() functions

	/**
	 * Helps converting PHP values into correct css sizes ( px, em, % )
	 * - Int becomes 0 or Npx
	 * - Float becomes 0 or Nem
	 * - Percent % and everything else remains untouched
	 *
	 * @param string|int|float $s
	 */
	private function size_Convert_Helper($s)
	{
		if($s === 0 || $s === 0.0)
			return '0';
		elseif(is_float($s))
			return round($s, 2).'em';
		elseif(is_int($s))
			return $s.'px';
		else
			return $s;
	}

	/**
	 * CSS data
	 * @var array
	 */
	protected $styles;

	/**
	 * Sets the data from which styles will be generated. Does some conversions.
	 *
	 * @see \DRY\Render\Transform_base::set_Data()
	 */
	public function set_Data()
	{
		$this-> styles = func_get_arg(0);

		$val = null; $val_elem = null;
		// some initial conversions
		foreach($this-> styles as &$styleset) {
			unset($val);
			foreach($styleset as $property=>&$val) {
				if(is_array($val)) { // some properties can take arrays as values, in which case some conversions are automatically done
					switch($property) {
						// Direct color properties
						case 'background-color':
						case 'color':
						case 'border-color':
						case 'border-top-color':
						case 'border-right-color':
						case 'border-bottom-color':
						case 'border-left-color':
						case 'column-rule-color': // CSS3
						case 'outline-color':
							if(count($val)==4) // assuming "rgba"
								if($val[0]=='#') {
									array_shift($val);
									$val = call_user_func_array(array(&$this, "hex"), $val);
								} else
									$val = call_user_func_array(array(&$this, "rgba"), $val);
							elseif(count($val)==3) // assuming "rgb" for 3 parameters
								$val = call_user_func_array(array(&$this, "rgb"), $val);
							else
								return false; // error, can't have an array of 1 or 2, or more than 4 values?
							break;

						// Properties which can have colors inside them
						case 'background':
						case 'border':
						case 'border-top':
						case 'border-right':
						case 'border-bottom':
						case 'border-left':
						case 'outline':
							unset($val_elem);
							foreach($val as &$val_elem) {
								if(is_array($val_elem))
									if(count($val_elem)==4) // assuming "rgba" or "#hex"
										if($val_elem[0]=='#') {
											array_shift($val_elem);
											$val_elem = call_user_func_array(array(&$this, "hex"), $val_elem);
										} else
											$val_elem = call_user_func_array(array(&$this, "rgba"), $val_elem);
									elseif(count($val_elem)==3) // assuming "rgb" for 3 parameters
										$val_elem = call_user_func_array(array(&$this, "rgb"), $val_elem);
									else
										return false; // error, can't have an array of 1 or 2, or more than 4 values?
								else
									$val_elem = $this-> size_Convert_Helper($val_elem);
							}
							// bleed through...

						// by default, attempt to convert array members using size_Convert_Helper, then concatenate values with space
						default:
							unset($val_elem);
							foreach($val as &$val_elem)
								$val_elem = $this-> size_Convert_Helper($val_elem);
							$val = implode(' ', $val);
					}
				} else {
					// attempt to convert array members using size_Convert_Helper (if not a string)
					$val = $this-> size_Convert_Helper($val);
				}
			}
		}
	}

	/**
	 * Output valid CSS from $this-> styles data
	 * @see \DRY\Render\Transform_base::__toString()
	 */
	public function __toString()
	{
		header("Content-type: text/css");
		$s = '/* Generated by DonReY Render Module - CSS Transform */'.PHP_EOL;

		foreach($this-> styles as $selectors=>$styleset) {
			$s.= PHP_EOL.$selectors.' {'.PHP_EOL;
			foreach($styleset as $property=>$val)
				$s.= "\t{$property}: {$val};".PHP_EOL;
			$s.= '}'.PHP_EOL;
		}
		return $s;
	}
}